package com.sundablog.mvc.V1;

import com.sundablog.mvc.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.logging.XMLFormatter;

/**
 * @Title:
 * @Package com.sundablog.mvc.V1
 * @Description: TODO
 * @author: 孙达
 * @date: 2020/04/01 17:30
 * @Copyright: All rights Reserved, Designed By www.sundablog.com
 */
public class SDDispatchServlet extends HttpServlet {

    private Properties contextConfig = new Properties();

    private List<String> classNames = new ArrayList<String>();

    private Map<String,Object> ioc = new HashMap<String, Object>();

    private Map<String,Method> handlerMapping = new HashMap<String, Method>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws  IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws  IOException {
        //委派
        try {
            doDispatch(req,resp);
        } catch (Exception e){
            e.printStackTrace();
            resp.getWriter().write("500 exception,Detail");
        }

    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath,"").replaceAll("/+","/");

        if (!this.handlerMapping.containsKey(url)){
            resp.getWriter().write("404 Not Found!");
            return;
        }

        Map<String,String[]> params = req.getParameterMap();

        Method method = this.handlerMapping.get(url);

        //获取形参列表
        Class<?> [] parameterTypes = method.getParameterTypes();
        Object [] paramValues = new Object[parameterTypes.length];

        for (int i = 0; i < parameterTypes.length; i++) {
            Class paramterType = parameterTypes[i];
            if(paramterType == HttpServletRequest.class){
                paramValues[i] = req;
            }else if(paramterType == HttpServletResponse.class){
                paramValues[i] = resp;
            }else if(paramterType == String.class){
                //通过运行时的状态去拿到你
                Annotation[] [] pa = method.getParameterAnnotations();
                for (int j = 0; j < pa.length ; j ++) {
                    for(Annotation a : pa[i]){
                        if(a instanceof SDRequestParam){
                            String paramName = ((SDRequestParam) a).value();
                            if(!"".equals(paramName.trim())){
                                String value = Arrays.toString(params.get(paramName))
                                        .replaceAll("\\[|\\]","")
                                        .replaceAll("\\s+",",");
                                paramValues[i] = value;
                            }
                        }
                    }
                }

            }
        }



        //暂时硬编码
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        //赋值实参列表
        method.invoke(ioc.get(beanName),paramValues);



    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1、加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2、扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));

        //=================Ioc部分==============================

        //3、初始化IOC容器、将扫描到类实例化,保存到IOC容器中
        doInstance();

        /**
         * 如果以后加入aop的话在DI之前
         */

        //===================DI部分==============================
        //4、完成依赖注入
        doAutowired();

        //===================MVC==============================
        //5、初始化HandlerMapping
        doHandlerMapping();

        System.out.println("初始化完成");

    }


    /**
     * 读取配置文件
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            //读取properties文件
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭流
            if (null != is){
                try {
                    is.close();
                } catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 扫描相关的类
     * @param scanPackage
     */
    private void doScanner(String scanPackage) {
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classPath = new File(url.getFile());
        //当成一个classPath文件夹
        for (File file : classPath.listFiles()){
            //文件夹递归
            if (file.isDirectory()){
                doScanner(scanPackage + "." + file.getName());
            } else {
                //判断是否是class文件
                if (!file.getName().endsWith(".class")){
                    continue;
                }
                //去除.class
                String className = scanPackage +"."+ file.getName().replace(".class", "");
                classNames.add(className);
            }


        }

    }


    private void doInstance() {
        if (classNames.isEmpty()){
            return;
        }
        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);

                //判断是否加了@SDController 和 @SDService 注解
                if (clazz.isAnnotationPresent(SDController.class) ){
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    //Ioc的Value
                    Object instance = clazz.newInstance();
                    ioc.put(beanName,instance);
                } else if (clazz.isAnnotationPresent(SDService.class)){
                    //1、在多个包下出现相同的类名，自己起名字（自定义命名）
                    String beanName = clazz.getAnnotation(SDService.class).value();
                    //判断有没有自定义类名 如果没有就默认首字母小写
                    if ("".equals(beanName.trim())){
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    //2、默认的类名首字母小写
                    Object instance = clazz.newInstance();
                    ioc.put(beanName,instance);

                    //3、如果是接口  判断有多少个实现，如果只有一个，默认就选择这个实现类，如果有多个 就只能异常

                    for (Class<?> i : clazz.getInterfaces()) {
                        //判断如果接口有多个实现类 如果存在抛出异常 需要自定义类名
                        if (ioc.containsKey(i.getName())){
                            throw new Exception("The" + i.getName() + "is exists");
                        }
                        ioc.put(i.getName(),instance);
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



    private void doAutowired() {
        //判断ioc是否为空  如果是 直接返回
        if (ioc.isEmpty()){
            return;
        }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {

            //遍历循环所有声明的字段
            for (Field field : entry.getValue().getClass().getDeclaredFields()) {

                //判断是否加了SDAutowired注解
                if (!field.isAnnotationPresent(SDAutowired.class)){
                    return;
                }

                SDAutowired autowired = field.getAnnotation(SDAutowired.class);
                //判断以后没有自定义beanName，就默认根据类型
                String beanName = autowired.value().trim();
                if ("".equals(beanName)){
                    beanName = field.getType().getName();
                }
                //暴力访问
                field.setAccessible(true);

                try {
                    //ioc.get(beanName) 相当于通过接口的全名拿到接口的实现的实列
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }


            }



        }
    }
    private void doHandlerMapping() {

        if (ioc.isEmpty()){
            return;
        }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();

            //判断是否加了SDController注解
            if (!clazz.isAnnotationPresent(SDController.class)){
                continue;
            }

            //获取class上的URL
            String baseUrl = "";
            if (clazz.isAnnotationPresent(SDRequestMapping.class)){
                SDRequestMapping requestMapping = clazz.getAnnotation(SDRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            //获取所有 public
            for (Method method : clazz.getMethods()) {
                //判断是否加了SDRequestMapping注解
                if (!method.isAnnotationPresent(SDRequestMapping.class)){
                    continue;
                }
                //提前每个方法上的URL
                SDRequestMapping requestMapping = method.getAnnotation(SDRequestMapping.class);
                //使用正则表达式无论有多少个/替换成一个/

                String url = ("/"+baseUrl + "/" + requestMapping.value()).replaceAll("/+","/");
                handlerMapping.put(url,method);
                System.out.println("Mapped : " + url + "," + method);
            }

        }

    }



    /**
     * 首字母小写
     * @param simpleName
     * @return
     */
    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}
